/*
 * Copyright (c) 2012-2017 Red Hat, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Red Hat, Inc. - initial API and implementation
 */
package org.eclipse.che.plugin.docker.machine;

import static java.util.stream.Collectors.toMap;

import com.google.inject.assistedinject.Assisted;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import javax.inject.Inject;
import javax.inject.Named;
import org.eclipse.che.api.core.model.machine.MachineConfig;
import org.eclipse.che.api.core.model.machine.MachineRuntimeInfo;
import org.eclipse.che.api.core.model.machine.ServerConf;
import org.eclipse.che.api.machine.server.model.impl.ServerConfImpl;
import org.eclipse.che.api.machine.server.model.impl.ServerImpl;
import org.eclipse.che.plugin.docker.client.json.ContainerConfig;
import org.eclipse.che.plugin.docker.client.json.ContainerInfo;
import org.eclipse.che.plugin.docker.client.json.ContainerState;
import org.eclipse.che.plugin.docker.client.json.HostConfig;
import org.eclipse.che.plugin.docker.client.json.NetworkSettings;

/**
 * Docker implementation of {@link MachineRuntimeInfo}
 *
 * @author andrew00x
 * @author Alexander Garagatyi
 * @author Roman Iuvshyn
 */
public class DockerInstanceRuntimeInfo implements MachineRuntimeInfo {
  /** Env variable that points to root folder of projects in dev machine */
  public static final String PROJECTS_ROOT_VARIABLE = "CHE_PROJECTS_ROOT";

  /** Env variable for jvm settings */
  public static final String JAVA_OPTS_VARIABLE = "JAVA_OPTS";

  /** Env variable for dev machine that contains url of Che API */
  public static final String API_ENDPOINT_URL_VARIABLE = "CHE_API";

  /**
   * Environment variable that will contain ID of a workspace for which this machine has been
   * created
   */
  public static final String CHE_WORKSPACE_ID = "CHE_WORKSPACE_ID";

  /** Environment variable that will contain Name of the machine */
  public static final String CHE_MACHINE_NAME = "CHE_MACHINE_NAME";

  /** Environment variable that will contain Name of the machine */
  public static final String CHE_IS_DEV_MACHINE = "CHE_IS_DEV_MACHINE";

  /**
   * Default HOSTNAME that will be added in all docker containers that are started. This host will
   * container the Docker host's ip reachable inside the container.
   */
  public static final String CHE_HOST = "che-host";

  /** Environment variable that will be setup in developer machine and contains user token. */
  public static final String USER_TOKEN = "USER_TOKEN";

  private final ContainerInfo info;
  private final Map<String, ServerConfImpl> serversConf;
  private final String internalHost;
  private final ServerEvaluationStrategyProvider provider;

  @Inject
  public DockerInstanceRuntimeInfo(
      @Assisted ContainerInfo containerInfo,
      @Assisted MachineConfig machineConfig,
      @Assisted String internalHost,
      ServerEvaluationStrategyProvider provider,
      @Named("machine.docker.dev_machine.machine_servers") Set<ServerConf> devMachineSystemServers,
      @Named("machine.docker.machine_servers") Set<ServerConf> allMachinesSystemServers) {
    this.info = containerInfo;

    Stream<ServerConf> confStream =
        Stream.concat(machineConfig.getServers().stream(), allMachinesSystemServers.stream());
    if (machineConfig.isDev()) {
      confStream = Stream.concat(confStream, devMachineSystemServers.stream());
    }
    // convert list to map for quick search and normalize port - add /tcp if missing
    this.serversConf =
        confStream.collect(
            toMap(
                srvConf ->
                    srvConf.getPort().contains("/")
                        ? srvConf.getPort()
                        : srvConf.getPort() + "/tcp",
                ServerConfImpl::new));

    this.internalHost = internalHost;
    this.provider = provider;
  }

  @Override
  public Map<String, String> getProperties() {
    Map<String, String> md = new LinkedHashMap<>();
    md.put("id", info.getId());
    md.put("created", info.getCreated());
    md.put("image", info.getImage());
    md.put("path", info.getPath());
    md.put("appArmorProfile", info.getAppArmorProfile());
    md.put("driver", info.getDriver());
    md.put("execDriver", info.getExecDriver());
    md.put("hostnamePath", info.getHostnamePath());
    md.put("hostsPath", info.getHostsPath());
    md.put("mountLabel", info.getMountLabel());
    md.put("name", info.getName());
    md.put("processLabel", info.getProcessLabel());
    md.put("volumesRW", String.valueOf(info.getVolumesRW()));
    md.put("resolvConfPath", info.getResolvConfPath());
    md.put("args", Arrays.toString(info.getArgs()));
    md.put("volumes", String.valueOf(info.getVolumes()));
    md.put("restartCount", String.valueOf(info.getRestartCount()));
    md.put("logPath", String.valueOf(info.getLogPath()));
    ContainerConfig config = info.getConfig();
    if (config != null) {
      md.put("config.domainName", config.getDomainName());
      md.put("config.hostname", config.getHostname());
      md.put("config.image", config.getImage());
      md.put("config.user", config.getUser());
      md.put("config.workingDir", config.getWorkingDir());
      md.put("config.cmd", Arrays.toString(config.getCmd()));
      md.put("config.volumes", String.valueOf(config.getVolumes()));
      md.put("config.entrypoint", Arrays.toString(config.getEntrypoint()));
      md.put("config.exposedPorts", String.valueOf(config.getExposedPorts()));
      md.put("config.macAddress", config.getMacAddress());
      md.put("config.securityOpts", Arrays.toString(config.getSecurityOpts()));
      md.put("config.env", Arrays.toString(config.getEnv()));
      md.put("config.attachStderr", Boolean.toString(config.isAttachStderr()));
      md.put("config.attachStdin", Boolean.toString(config.isAttachStdin()));
      md.put("config.attachStdout", Boolean.toString(config.isAttachStdout()));
      md.put("config.networkDisabled", Boolean.toString(config.isNetworkDisabled()));
      md.put("config.openStdin", Boolean.toString(config.isOpenStdin()));
      md.put("config.stdinOnce", Boolean.toString(config.isStdinOnce()));
      md.put("config.tty", Boolean.toString(config.isTty()));
      md.put("config.labels", String.valueOf(config.getLabels()));
    }
    ContainerState state = info.getState();
    if (state != null) {
      md.put("state.startedAt", state.getStartedAt());
      md.put("state.exitCode", Integer.toString(state.getExitCode()));
      md.put("state.pid", Integer.toString(state.getPid()));
      md.put("state.running", Boolean.toString(state.isRunning()));
      md.put("state.finishedAt", state.getFinishedAt());
      md.put("state.paused", Boolean.toString(state.isPaused()));
      md.put("state.restarting", Boolean.toString(state.isRestarting()));
      md.put("state.dead", String.valueOf(state.isDead()));
      md.put("state.OOMKilled", String.valueOf(state.isOOMKilled()));
      md.put("state.error", state.getError());
    }
    NetworkSettings networkSettings = info.getNetworkSettings();
    if (networkSettings != null) {
      md.put("network.bridge", networkSettings.getBridge());
      md.put("network.gateway", networkSettings.getGateway());
      md.put("network.ipAddress", networkSettings.getIpAddress());
      md.put("network.ipPrefixLen", Integer.toString(networkSettings.getIpPrefixLen()));
      md.put("network.portMappings", Arrays.toString(networkSettings.getPortMapping()));
      md.put("network.macAddress", networkSettings.getMacAddress());
      md.put("network.ports", String.valueOf(networkSettings.getPorts()));
      md.put(
          "network.linkLocalIPv6PrefixLen",
          String.valueOf(networkSettings.getLinkLocalIPv6PrefixLen()));
      md.put("network.globalIPv6Address", networkSettings.getGlobalIPv6Address());
      md.put(
          "network.globalIPv6PrefixLen", String.valueOf(networkSettings.getGlobalIPv6PrefixLen()));
      md.put("network.iPv6Gateway", networkSettings.getIpV6Gateway());
      md.put("network.linkLocalIPv6Address", networkSettings.getLinkLocalIPv6Address());
    }
    HostConfig hostConfig = info.getHostConfig();
    if (hostConfig != null) {
      md.put("hostConfig.cgroupParent", hostConfig.getCgroupParent());
      md.put("hostConfig.containerIDFile", hostConfig.getContainerIDFile());
      md.put("hostConfig.cpusetCpus", hostConfig.getCpusetCpus());
      md.put("hostConfig.ipcMode", hostConfig.getIpcMode());
      md.put("hostConfig.memory", Long.toString(hostConfig.getMemory()));
      md.put("hostConfig.networkMode", hostConfig.getNetworkMode());
      md.put("hostConfig.pidMode", hostConfig.getPidMode());
      md.put("hostConfig.binds", Arrays.toString(hostConfig.getBinds()));
      md.put("hostConfig.capAdd", Arrays.toString(hostConfig.getCapAdd()));
      md.put("hostConfig.capDrop", Arrays.toString(hostConfig.getCapDrop()));
      md.put("hostConfig.cpuShares", String.valueOf(hostConfig.getCpuShares()));
      md.put("hostConfig.devices", Arrays.toString(hostConfig.getDevices()));
      md.put("hostConfig.dns", Arrays.toString(hostConfig.getDns()));
      md.put("hostConfig.dnsSearch", Arrays.toString(hostConfig.getDnsSearch()));
      md.put("hostConfig.extraHosts", Arrays.toString(hostConfig.getExtraHosts()));
      md.put("hostConfig.links", Arrays.toString(hostConfig.getLinks()));
      md.put("hostConfig.logConfig", String.valueOf(hostConfig.getLogConfig()));
      md.put("hostConfig.lxcConf", Arrays.toString(hostConfig.getLxcConf()));
      md.put("hostConfig.memorySwap", String.valueOf(hostConfig.getMemorySwap()));
      md.put("hostConfig.portBindings", String.valueOf(hostConfig.getPortBindings()));
      md.put("hostConfig.restartPolicy", String.valueOf(hostConfig.getRestartPolicy()));
      md.put("hostConfig.ulimits", Arrays.toString(hostConfig.getUlimits()));
      md.put("hostConfig.volumesFrom", Arrays.toString(hostConfig.getVolumesFrom()));
      md.put("hostConfig.pidsLimit", Long.toString(hostConfig.getPidsLimit()));
    }

    return md;
  }

  @Override
  public Map<String, String> getEnvVariables() {
    final Map<String, String> envVariables = new HashMap<>();
    if (info.getConfig() != null && info.getConfig().getEnv() != null) {
      for (String envVariable : info.getConfig().getEnv()) {
        final String[] variableNameValue = envVariable.split("=", 2);
        envVariables.put(variableNameValue[0], variableNameValue[1]);
      }
    }
    return envVariables;
  }

  @Override
  public String projectsRoot() {
    return getEnvVariables().get(PROJECTS_ROOT_VARIABLE);
  }

  @Override
  public Map<String, ServerImpl> getServers() {
    ServerEvaluationStrategy strategy = provider.get();
    return strategy.getServers(info, internalHost, serversConf);
  }
}
